package com.nage.engine;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import com.nage.components.Component;
import com.nage.multiplayer.MultiplayerSystem;
import com.nage.utils.ByteArrayBuilder;

public class GameObject {
	
	public float x;
	public float y;
	
	public GameGraph parent;

	public String m_Name;
	ArrayList<Component> objectComponents;
	ArrayList<Component> removeQueue;
	private	ArrayList<Component> addQueue;

	public boolean hasMoved;
	
	public boolean remoteObject, localObject; // remote objects will use remote input. local objects use local input. can both be true too.
	public byte m_Owner;
	
	boolean remotelyUpdated;
	
	/** Set to true when the object is modified and this will enable the
	 * changes to be propagated across multiplayer system.
	 */
	public boolean modified;
	
	/**
	 * Unique id for this object that allows it to be identified across
	 * multiplayer system.
	 */
	public byte id; 
	
	/**
	 * Assigned to a GameObject when it is created. Make sure this is incremented
	 * so that each GameObject has a unique id.
	 */
	private static byte next_id = 0x01;

	
	public GameObject(GameGraph p) {
		objectComponents = new ArrayList<Component>();
		removeQueue = new ArrayList<Component>();
		addQueue = new ArrayList<Component>();
		hasMoved = true;
		parent = p;
		localObject = true; // objects are by default local and not remote.
		remoteObject = false;
		m_Owner = MultiplayerSystem.DEFAULT;
		id = next_id++;
	}
	
	public void addComponentWhenPossible(Component c) {
		addQueue.add(c);
	}
	public void addComponent(Component c) {
		objectComponents.add(c);
	}
	
	public Component getComponent(int id) {
		for (Component c : objectComponents) {
			if(c.type==id) {
				return c;
			}
		}
		return null;
	}
	
	public boolean hasComponent(int id) {
		for (Component c : objectComponents) {
			if(c.type==id) {
				return true;
			}
		}
		return false;
	}

	public void removeComponent(int id) {
		for (Component c : objectComponents) {
			if(c.type==id) {
				removeQueue.add(c);
			}
		}
	}
	
	public void removeComponent(Component c) {
		removeQueue.add(c);
	}
	
	public void update(long delta) {
		
	//	Log.d("GameObject", "	update() start: "+name+" x = "+x+" y = "+y);
		
		if(!removeQueue.isEmpty()) {
			for(Component c : removeQueue) {
				objectComponents.remove(c);
			}
			removeQueue.clear();
		}
		if(!addQueue.isEmpty()) {
			for(Component c : addQueue) {
				objectComponents.add(c);
			}
			addQueue.clear();
		}
		

		for (Component c : objectComponents) {
			c.update(delta);
		}			
		modified = true;
		
	}

	public void setX(float x) {
		this.x = x;
		hasMoved = true;
	}

	public float getX() {
		return x;
	}

	public void setY(float y) {
		this.y = y;
		hasMoved = true;
	}

	public float getY() {
		return y;
	}

	public void setX(String x) {
		if(x!=null) {
			this.x = (float)Float.parseFloat(x);
			hasMoved = true;
		}
	}
	
	public void setY(String y) {
		if(y!=null) {
			this.y = (float)Float.parseFloat(y);
			hasMoved = true;
		}
	}

	/**
	 * Visit each component in this object and tells it to perform its
	 * flatten routine using the stream_builder. Adds the overall count
	 * of components, and a header for each component.
	 * 
	 * @param stream_builder
	 * @return
	 * @throws IOException
	 */
	public boolean flatten(ByteArrayOutputStream stream_builder) throws IOException {
		
		stream_builder.write(id); // first send obj identification.
		stream_builder.write(m_Name.getBytes()); // write name to byte array
		stream_builder.write(0); // null terminate the string
		stream_builder.write(ByteArrayBuilder.floatToByteArray(x)); // write member variable x
		stream_builder.write(ByteArrayBuilder.floatToByteArray(y)); // write member variable y
		
		
		stream_builder.write(getModifiedCount()); // number of components this object has
		int comp_number = 1;
		for(Component comp : objectComponents) {
			if(comp.modified) {
			//	Log.d("GameObject", "COMP "+comp.id+" ("+comp.getType()+") MODIFIED");
				stream_builder.write(comp_number++); // component number
				comp.flatten(stream_builder); // component contents
				comp.modified = false; // reset modified.
			}
		}
		return comp_number==objectComponents.size();
	}

	 /** Counts the number of objects within graph that have their modified
	 * flag set to true. 
	 * @return Number of modified objects in graph
	 */
	private int getModifiedCount() {
		int i = 0;
		for(Component comp : objectComponents) {
			if(comp.modified)
				i++;
		}
		return i;
	}

	/**
	 * Expands the byte array b starting at index i. It will check the count of
	 * components contained in the array, and create that many blank components. Each
	 * blank component is then asked to expand itself, being given the buffer and an
	 * index to start expanding from.
	 * 
	 * @param b Byte array containing a flattened game graph
	 * @param i Index in array that has been already expanded
	 * @return  
	 * @throws GameGraphException 
	 */
	public int expand(byte[] b, int i) throws GameGraphException {
		
		id = b[i++]; // read id
		
		StringBuffer str = new StringBuffer();
		while(b[i]!=0) { // null terminated, so read till 0 is found.
			str.append((char)b[i++]);
		}
		i++;
		m_Name = str.toString(); // read name.
		x = ByteArrayBuilder.byteArrayToFloat(b[i++], b[i++], b[i++], b[i++]); // set x from bytestream
		y = ByteArrayBuilder.byteArrayToFloat(b[i++], b[i++], b[i++], b[i++]); // set y from bytestream
		
		int expected_size = b[i++]; // the is the size of the objectComponents graph
									// of the flattened game object. (number of components
									// this object has.
	//	Log.d("GameObject", "          Expanding "+expected_size+" Components");

		for(int j = 1; j <= expected_size; j++) {
			if(b[i++]!=j) { throw new GameGraphException("Component numbers do not match"); }
			Component comp = Component.createComponent(b[i++], this);
	//		Log.d("GameObject", "	       Created a blank "+comp.toString());
			i = comp.expand(b, i);
			addComponent(comp);
			
		}
		remotelyUpdated = true;
		return i;
	}

	public void setOwner(String str) {
		if(str == null) {
			m_Owner = MultiplayerSystem.DEFAULT;
		} else if(str.equalsIgnoreCase("server")){
			m_Owner = MultiplayerSystem.SERVER;
		} else if(str.equalsIgnoreCase("client")) {
			m_Owner = MultiplayerSystem.CLIENT;
		}
	}

	public void setName(String str) {
		if(str == null) {
			m_Name = "gameobject";
		} else {
			m_Name = str;
		}
	}

	/**
	 * Called when a GameObject is finished being built (every component that
	 * it has has been instantiated and added). This will link each component 
	 * with any others that it wants to have a reference to. If a component
	 * wants a reference to a Component that doesn't exist and this will
	 * cause a critical error, then CRITICAL ERROR!
	 * 
	 * @return Whether the linking was succesful or not.
	 */
	public boolean linkComponents() throws GameGraphException {
		for(Component c : objectComponents) {
			if(!c.findLinks()) {
				return false;
			}
		}
		
		
		return true;
	}



}
